#ifndef D_VECTOR3_H
#define D_VECTOR3_H

#include <math.h>
#include <helpers\math\math_helper.h>

namespace DelGE
{
	namespace Helpers
	{
		namespace Math
		{
			struct Vector3
			{
				float x, y, z, w;

				static Vector3 Lerp(const Vector3& rVector0, const Vector3& rVector1, float fLerpAmount);

				Vector3();
				Vector3(float x, float y);
				Vector3(float x, float y, float z);
				~Vector3();

				static Vector3 Zero();
				static Vector3 VectorFromAngle(float fAngle);

				float Length();
				void Normailize();

				void Reflect(const Vector3& rOther);

				float DotProduct(const Vector3& rOther) const;
				Vector3 CrossProduct(const Vector3& rOther) const;

				Vector3& operator =(const Vector3& rVector0);
				Vector3& operator +=(const Vector3& rVector0);
				Vector3& operator +=(float fValue);
				Vector3& operator *=(float fScalar);
				Vector3& operator /=(float fScalar);
				float& operator [](unsigned int uiIndex);
				float operator [](unsigned int uiIndex) const;
			};

			bool operator ==(const Vector3& rVector0, const Vector3& rVector1);
			bool operator !=(const Vector3& rVector0, const Vector3& rVector1);

			Vector3 operator *(const Vector3& rVector0, float fScalar);
			Vector3 operator /(const Vector3& rVector0, float fScalar);
			Vector3 operator *(float fScalar, const Vector3& rVector0);
			Vector3 operator /(float fScalar, const Vector3& rVector0);

			Vector3 operator *(const Vector3& rVector0, const Vector3& rVector1);

			Vector3 operator +(const Vector3& rVector0, float fValue);
			Vector3 operator +(const Vector3& rVector0, const Vector3& rVector1);

			Vector3 operator -(const Vector3& rVector0, float fValue);
			Vector3 operator -(const Vector3& rVector0, const Vector3& rVector1);

			inline float Vector3::Length()
			{
				return sqrt((this->x * this->x) + (this->y * this->y) + (this->z * this->z));
			}

			inline void Vector3::Normailize()
			{
				float length = this->Length();

				this->x /= length;
				this->y /= length;
				this->z /= length;
			}

			inline void Vector3::Reflect(const Vector3& rOther)
			{
				Vector3 reflection = (*this) - 2.0f * rOther * (rOther.DotProduct(*this));

				*this = reflection;
			}

			inline float Vector3::DotProduct(const Vector3& rOther) const
			{
				return ((this->x * rOther.x) + 
						(this->y * rOther.y) +
						(this->z * rOther.z));
			}

			inline Vector3 Vector3::CrossProduct(const Vector3& rOther) const
			{
				Vector3 result;

				result.x = (this->y * rOther.z) - (this->z * rOther.y);
				result.y = (this->z * rOther.x) - (this->x * rOther.z);
				result.z = (this->x * rOther.y) - (this->y * rOther.x);

				return result;
			}

			inline Vector3& Vector3::operator =(const Vector3& rVector0)
			{
				this->x = rVector0.x;
				this->y = rVector0.y;
				this->z = rVector0.z;

				return *this;
			}

			inline Vector3& Vector3::operator +=(const Vector3& rVector0)
			{
				this->x += rVector0.x;
				this->y += rVector0.y;
				this->z += rVector0.z;

				return *this;
			}
			
			inline Vector3& Vector3::operator +=(float fValue)
			{
				this->x += fValue;
				this->y += fValue;
				this->z += fValue;

				return *this;
			}

			inline Vector3& Vector3::operator *=(float fScalar)
			{
				this->x *= fScalar;
				this->y *= fScalar;
				this->z *= fScalar;

				return *this;
			}

			inline Vector3& Vector3::operator /=(float fScalar)
			{
				this->x /= fScalar;
				this->y /= fScalar;
				this->z /= fScalar;

				return *this;
			}

			inline float& Vector3::operator [](unsigned int uiIndex)
			{
				if (uiIndex > 3) uiIndex = 3;
				switch (uiIndex)
				{
					case 0: return this->x;
					case 1: return this->y;
					case 2: return this->z;
					case 3: return this->w;
				}
				return this->x;
			}

			inline float Vector3::operator [](unsigned int uiIndex) const
			{
				if (uiIndex > 3) uiIndex = 3;
				switch (uiIndex)
				{
					case 0: return this->x;
					case 1: return this->y;
					case 2: return this->z;
					case 3: return this->w;
				}
				return 0;
			}

			inline bool operator ==(const Vector3& rVector0, const Vector3& rVector1)
			{
				return ((rVector0.x == rVector1.x) &&
						(rVector0.y == rVector1.y) &&
						(rVector0.z == rVector1.z));
			}

			inline bool operator !=(const Vector3& rVector0, const Vector3& rVector1)
			{
				return !(rVector0 == rVector1);
			}

			inline Vector3 operator *(const Vector3& rVector0, float fScalar)
			{
				return Vector3(rVector0.x * fScalar, 
							   rVector0.y * fScalar,
							   rVector0.z * fScalar);
			}

			inline Vector3 operator /(const Vector3& rVector0, float fScalar)
			{
				return Vector3(rVector0.x / fScalar, 
							   rVector0.y / fScalar,
							   rVector0.z / fScalar);
			}

			inline Vector3 operator *(float fScalar, const Vector3& rVector0)
			{
				return Vector3(rVector0.x * fScalar, 
							   rVector0.y * fScalar,
							   rVector0.z * fScalar);
			}

			inline Vector3 operator /(float fScalar, const Vector3& rVector0)
			{
				return Vector3(rVector0.x / fScalar, 
							   rVector0.y / fScalar,
							   rVector0.z / fScalar);
			}

			inline Vector3 operator *(const Vector3& rVector0, const Vector3& rVector1)
			{
				return Vector3(rVector0.x * rVector1.x, 
							   rVector0.y * rVector1.y,
							   rVector0.z * rVector1.z);
			}

			inline Vector3 operator +(const Vector3& rVector0, float fValue)
			{
				return Vector3(rVector0.x + fValue,
							   rVector0.y + fValue,
							   rVector0.z + fValue);
			}

			inline Vector3 operator +(const Vector3& rVector0, const Vector3& rVector1)
			{
				return Vector3(rVector0.x + rVector1.x,
							   rVector0.y + rVector1.y,
							   rVector0.z + rVector1.z);
			}

			inline Vector3 operator -(const Vector3& rVector0, float fValue)
			{
				return Vector3(rVector0.x - fValue,
							   rVector0.y - fValue,
							   rVector0.z - fValue);
			}

			inline Vector3 operator -(const Vector3& rVector0, const Vector3& rVector1)
			{
				return Vector3(rVector0.x - rVector1.x,
							   rVector0.y - rVector1.y,
							   rVector0.z - rVector1.z);
			}
		}
	}
}

#endif